Most computer systems execute under the control of a multiprocessing operating system. A multiprocessing operating system is designed to control the concurrent execution of multiple computer programs. Such operating systems typically allow one program to execute for a while, and then allow another computer program to execute. Before each computer program is allowed to execute, the operating system initializes the system resources (e.g., is CPU registers) to their state when that computer program last executed and, if necessary, loads the computer program back into memory. Thus, when the computer program is allowed to execute again, its execution continues where it left off. Each executing computer program is generally referred to as a "process," and the state of the computer system when that computer program executes is referred to as the "context" of the process.
Computer programs that are executing concurrently may have the need to communicate with each other. A computer application may be implemented as multiple cooperating computer programs that each execute as separate processes. One computer program may request another computer program to perform a certain function on its behalf. Each time the operating system allows a different computer program to execute, the operating system performs what is referred to as a process context switch. The context switch involves saving the current state of the executing process so that it can continue execution at a later time, and restoring the previous state of the computer program to be executed. The saving and restoring of the contexts may place an unacceptable overhead on the computer system. To avoid the overhead of process context switching, some operating systems have implemented the concept of a lightweight process or a thread. Within a single process, the operating system may allow several threads to execute concurrently. Each thread in a process shares the same address space (i.e., code and data), but each thread has its own program counter and other register values. The operating system allows each thread in the process to execute for a while and then switches control to the next thread. Because the threads share the same address space, the context switching from one thread to another can be accomplished much faster than switching from one process to another. Thus, the concept of multithreading allows many of the advantages of multiprocessing, but without the overhead of process context switches.
Many server computer programs (i.e., servers) that provide services to multiple client computer programs (i.e., clients) are implemented using multiple threads. When a client requests the server to perform a service on behalf of the client, the server will create a thread to provide that service for the client. Thus, when multiple clients request services at approximately the same time, the server will be executing many threads. The data that a process accesses is often organized into objects that encapsulate data and code for accessing that data. Because the threads within a process share the same data, access control to the data must be coordinated by the threads. With such a multithreading server, the developer of the server is responsible for coordinating access to the objects so that the processing by one thread will not be interfered with by another thread. For example, the developer may need to ensure that appropriate synchronization mechanism (e.g., locks and semaphores) are defined for each object. A synchronization problem could occur when two threads attempt to access an object concurrently. For example, if both threads A and B need to modify the same object, then a synchronization problem could occur in the following way. When the object provides a method to increment a data member, the method may load the value of the data member into a register, increment the value, and store the value back into the data member. However, if thread A invokes the method, the current value (e.g., 10) of the data member may get loaded into a register and then a thread context switch may occur so that thread B executes. If thread B also invokes the method, the current value (e.g., 10) of the data member would be loaded into a register, incremented (e.g., 11), and stored back into the data member. When thread A again executes, it resumes execution of the method. In this case, the value (i.e., 10) in its register is incremented (e.g., 11) and stored back into the data member. Thus, after execution of the method by both threads the data member will have a value of 11 rather than the expected value of 12. It may take a considerable amount of programming to develop a computer program that avoids various synchronization problems. The synchronization problems also occur when multiple processes, even with only one thread each, access shared memory. Thus, in general the problems occur when any threads, regardless of whether they are in the same or different processes, access objects in shared memory.
To help simplify development of such objects, an apartment model has been developed. According to the apartment model, each object can only be accessed by the single thread. Because each object can only be accessed by a single thread no synchronization problems occur as a result of multiple threads accessing the object concurrently. That is, the single thread can serially process each request to access the object. Thus, when a thread wants to access an object, it requests that the thread for the object perform the function on behalf of the requesting thread. The group of objects that can be accessed by a thread is referred to as an apartment. The apartment model is described in U.S. patent Ser. No. 08/381,635, entitled "Method and System for Multi-Threaded Processing," which is hereby incorporated by reference. A preferred technique for sending the request from one thread to another is referred to as marshalling and described in U.S. patent Ser. No. 5,511,197, entitled "Method and System for Network Marshalling of Interface Pointers for Remote Procedure Calls," which is hereby incorporated by reference.
When a programmer is developing a new object, the programmer needs to make a decision as to whether the object will be accessed only by a single thread or by multiple threads. If the object is to be accessed only by a single thread (i.e., an apartment-threaded object), then the implementation of the lo object is fairly straightforward because synchronization problems can be avoided by serializing access. Conversely, if the object is to be accessed by multiple threads (i.e., a free-threaded object), then the appropriate synchronization management mechanisms need to be developed. Unfortunately, apartment-threaded objects cannot be accessed by a server developed to access objects from any thread, and vice versa. When free-threaded objects are being used, a server is developed to simply pass pointers to the objects from one thread to another thread. The receiving thread would access the object directly, regardless of which thread created the object. Thus, if such a server loaded an apartment-threaded object, synchronization problems may occur as multiple threads accessed the object concurrently. Similarly, if a free-threaded object were executed in an apartment thread, the object may block in a manner that would cause the apartment to deadlock. Consequently, each class of object has an associated thread type. The thread type of the class can be either apartment-threaded or free-threaded. If a server is developed to use the apartment model, the server cannot instantiate free-threaded objects. Conversely, if the server is developed to use the free-threaded objects (i.e., a free-threading model), then the process cannot instantiate apartment-threaded objects. Two other thread types can be defined: single-threaded objects and both-threaded objects. A single-threaded object (referred to as a main-threaded object in the following) is an apartment-threaded object that can only be loaded into the apartment for the main thread of the process. A both-threaded object is compatible with both the apartment model and the free-threading model and thus can be loaded by a process that uses either model.
Although the apartment model does simplify the development of objects, the model has several drawbacks. First, if the computer has multiple processors, then the object can only be accessed by the processor that is executing the single thread and the other processors may be idle. Conversely, with the free-threaded objects, each thread that is accessing an object may be executing simultaneously on separate processors. Second, remote procedure call (RPC) mechanisms typically provide a dedicated thread for receiving calls from other processes. Thus, a thread context switch from the dedicated thread to the single thread may be needed. Conversely, a free-threaded object could execute in the dedicated thread. Third, because requests are nested, rather than queued, it may be that a first request is never completely serviced. That is, the last request received is serviced before the previously received requests are serviced. For example, when thread A is executing a method that was called from another thread, it may call a method in thread B, which in turn calls a method in thread A. If thread A did not process the second call when it was received, then the first call would never complete. However, if a first client calls a method in thread A, and many other clients call methods in thread A before the call by the first process completes, then the call by the first client may never complete because the thread is busy executing subsequent calls. Conversely, the threads with the free-threaded objects execute independently.